<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>JSDoc: Source: Infinilist.js</title>

    <script src="scripts/prettify/prettify.js"> </script>
    <script src="scripts/prettify/lang-css.js"> </script>
    <!--[if lt IE 9]>
      <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
    <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
    <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>

<body>

<div id="main">

    <h1 class="page-title">Source: Infinilist.js</h1>

    



    
    <section>
        <article>
            <pre class="prettyprint source linenums"><code>"use strict";

/**
 * Provides scrolling lists with large data sets that behave in a very
 * performance-optimised fashion by controlling the DOM elements currently
 * on screen to ensure that only the visible elements are rendered and
 * all other elements are simulated by variable height divs at the top
 * and bottom of the scrolling list.
 *
 * This module requires that the AutoBind module is loaded before it
 * will work.
 *
 * Infinilists work from views and those views cannot have an $orderBy
 * clause in them because that would slow down rendering. Instead if you
 * wish to have your data ordered you have to create a temporary collection
 * from which your view feeds from and pre-order the data before inserting
 * it into the temporary collection.
 * @class Infinilist
 * @requires AutoBind
 */
var Shared = window.ForerunnerDB.shared,
	View = Shared.modules.View;

/**
 * Creates an infinilist instance.
 * @param {String} selector A jQuery selector targeting the element that
 * will contain the list items.
 * @param {String} template jQuery selector of the template to use when
 * rendering an individual list item.
 * @param {Object} options The options object.
 * @param {View} view The view to read data from.
 * @constructor
 */
var Infinilist = function (selector, template, options, view) {
	var self = this;

	selector = $(selector);

	options = options || {};

	self.options = options.infinilist || {};

	delete options.infinilist;

	self.skip = 0;
	self.limit = 0;
	self.ignoreScroll = false;
	self.previousScrollTop = 0;
	self.selector = selector;
	self.template = template;
	self.view = view;
	self.itemTopMargin = $("&lt;div class='il_topMargin'>&lt;/div>");
	self.itemContainer = $("&lt;div class='il_items'>&lt;/div>");
	self.itemBottomMargin = $("&lt;div class='il_bottomMargin'>&lt;/div>");
	self.total = self.view.from().count(self.options.countQuery);
	self.itemHeight(self.options.itemHeight);

	self.___fromChangeFunc = function () {
		// View data changed, recalculate total items count and check
		// that the currently displayed view data is correct by forcing
		// a scroll event after a height recalculation
		self.recalcHeight();
		self.scroll(true);
	};

	self.view.from().on('change', self.___fromChangeFunc);

	selector.append(self.itemTopMargin);
	selector.append(self.itemContainer);
	selector.append(self.itemBottomMargin);

	self.resize();

	view.link(self.itemContainer, template, options);

	selector.on('scroll', function () {
		// Debounce scroll event
		if (!self.scrollDebouceTimeout) {
			self.scrollDebouceTimeout = setTimeout(function () {
				self.scroll();
				self.scrollDebouceTimeout = 0;
			}, 16);
		}
	});

	$(window).on('resize', function () {
		// Debounce resize event
		if (self.resizeDebouceTimeout) {
			clearTimeout(self.resizeDebouceTimeout);
		}

		self.resizeDebouceTimeout = setTimeout(function () {
			self.resize();
		}, 16);
	});
};

Shared.addModule('Infinilist', Infinilist);
Shared.mixin(Infinilist.prototype, 'Mixin.Events');

Shared.synthesize(Infinilist.prototype, 'itemHeight', function (val) {
	var self = this;

	if (val !== undefined) {
		self._itemHeight = val;
		self.virtualHeight = self.total * self._itemHeight;
		self.resize();
	}

	return this.$super.apply(this, arguments);
});

Infinilist.prototype.recalcHeight = function () {
	var self = this;

	if (self._state !== 'dropped') {
		self.total = self.view.from().count(self.options.countQuery);
		self.virtualHeight = self.total * self._itemHeight;

		self.resize();
	}
};

/**
 * Handle screen resizing.
 */
Infinilist.prototype.resize = function () {
	var self = this,
		newHeight = self.selector.height(),
		skipCount,
		scrollTop = self.selector.scrollTop();

	if (self.oldHeight !== newHeight) {
		self.oldHeight = newHeight;

		// Calculate number of visible items
		self.maxItemCount = Math.ceil(newHeight / self._itemHeight);
		skipCount = Math.floor(scrollTop / self._itemHeight);

		self.skip = skipCount;
		self.limit = self.maxItemCount + 1;

		// Check if current range is different from existing range

		self.view.queryOptions(self.currentRange());

		self.itemBottomMargin.height(self.virtualHeight - (skipCount * self._itemHeight)- (self.maxItemCount * self._itemHeight));
	}
};

Infinilist.prototype.currentRange = function () {
	return {
		$skip: this.skip,
		$limit: this.limit
	};
};

Infinilist.prototype.scroll = function (force) {
	var self = this,
		delta,
		skipCount,
		scrollTop = self.selector.scrollTop();

	// Get the current scroll position
	delta = scrollTop - self.previousScrollTop;
	self.previousScrollTop = scrollTop;

	// Check if a scroll change occurred
	if (force || delta !== 0) {
		// Determine the new item range
		skipCount = Math.floor(scrollTop / self._itemHeight);

		self.skip = skipCount;
		self.view.queryOptions(self.currentRange());

		self.itemTopMargin.height(skipCount * self._itemHeight);
		self.itemBottomMargin.height(self.virtualHeight - (skipCount * self._itemHeight)- (self.maxItemCount * self._itemHeight));
	}

	self.emit('scroll');
};

Infinilist.prototype.scrollToQuery = function (query, options, callback) {
	var self = this,
		result,
		index,
		orderOp = {
			$orderBy: self.view.queryOptions().$orderBy
		},
		tmpColl,
		scrollPos;

	if (typeof options === 'function') {
		callback = options;
		options = undefined;
	}

	// Ensure options has properties we expect
	options = options || {};
	options.$inc = options.$inc !== undefined ? options.$inc : 0;
	options.$incItem = options.$incItem !== undefined ? options.$incItem : 0;

	// Run query and get first matching record (with current sort)
	result = self.view.from().findOne(query, orderOp);

	// Find the position of the element inside the current view
	// based on the sort order
	tmpColl = self.view.db().collection('tmpSortCollection');
	tmpColl.setData(self.view.from().find(self.view.query()));
	index = tmpColl.indexOf(result, orderOp);
	tmpColl.drop();

	if (index > -1) {
		scrollPos = ((index + options.$incItem) * self._itemHeight) + options.$inc;
		scrollPos = scrollPos > 0 ? scrollPos : 0;

		if (self.selector.scrollTop() !== scrollPos) {
			if (callback) {
				self.once('scroll', function () {
					callback();
				});
			}

			// Scroll the main element to the position of the item
			self.selector.scrollTop(scrollPos);
		} else {
			if (callback) { callback(); }
		}

		return true;
	}

	return false;
};

Infinilist.prototype.drop = function (callback) {
	var self = this;

	// Unlink the view from the dom
	self.view.unlink(self.itemContainer, self.template);

	// Stop listening for changes
	self.view.from().off('change', self.___fromChangeFunc);

	// Set state to dropped
	self._state = 'dropped';

	// Kill listeners
	self.selector.off('scroll');
	$(window).off('resize');

	// Remove references
	delete self.ignoreScroll;
	delete self.previousScrollTop;
	delete self._itemHeight;
	delete self.selector;
	delete self.template;
	delete self.view;
	delete self.itemTopMargin;
	delete self.itemContainer;
	delete self.itemBottomMargin;
	delete self.___fromChangeFunc;

	this.emit('drop', this);

	if (callback) { callback(false, true); }

	delete self._listeners;
};

View.prototype.infinilist = function (targetSelector, templateSelector, options) {
	var target = window.jQuery(targetSelector);

	if (templateSelector === undefined) {
		return target.data('infinilist');
	}

	target.data('infinilist', new Infinilist(targetSelector, templateSelector, options, this));
};

View.prototype.unInfinilist = function (targetSelector) {
	var target = window.jQuery(targetSelector);

	if (target.data('infinilist')) {
		target.data('infinilist').drop();
		target.removeData('infinilist');

		return true;
	}

	return false;
};

Shared.moduleFinished('AutoBind', function () {
	Shared.finishModule('Infinilist');
});

module.exports = Infinilist;</code></pre>
        </article>
    </section>




</div>

<nav>
    <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="ActiveBucket.html">ActiveBucket</a></li><li><a href="Angular.html">Angular</a></li><li><a href="AutoBind.html">AutoBind</a></li><li><a href="Collection.html">Collection</a></li><li><a href="CollectionGroup.html">CollectionGroup</a></li><li><a href="Condition.html">Condition</a></li><li><a href="Core.html">Core</a></li><li><a href="Db.html">Db</a></li><li><a href="Document.html">Document</a></li><li><a href="Grid.html">Grid</a></li><li><a href="Highchart.html">Highchart</a></li><li><a href="Index2d.html">Index2d</a></li><li><a href="IndexBinaryTree.html">IndexBinaryTree</a></li><li><a href="IndexHashMap.html">IndexHashMap</a></li><li><a href="Infinilist.html">Infinilist</a></li><li><a href="KeyValueStore.html">KeyValueStore</a></li><li><a href="Metrics.html">Metrics</a></li><li><a href="MyModule.html">MyModule</a></li><li><a href="NodeApiClient.html">NodeApiClient</a></li><li><a href="NodeApiServer.html">NodeApiServer</a></li><li><a href="NodeRAS.html">NodeRAS</a></li><li><a href="Odm.html">Odm</a></li><li><a href="OldView.html">OldView</a></li><li><a href="Operation.html">Operation</a></li><li><a href="Overload.html">Overload</a></li><li><a href="Overview.html">Overview</a></li><li><a href="Overview_init.html">init</a></li><li><a href="Path.html">Path</a></li><li><a href="Persist.html">Persist</a></li><li><a href="Procedure.html">Procedure</a></li><li><a href="ReactorIO.html">ReactorIO</a></li><li><a href="Section.html">Section</a></li><li><a href="Serialiser.html">Serialiser</a></li><li><a href="Shared.overload.html">overload</a></li><li><a href="View.html">View</a></li></ul><h3>Mixins</h3><ul><li><a href="ChainReactor.html">ChainReactor</a></li><li><a href="Common.html">Common</a></li><li><a href="Constants.html">Constants</a></li><li><a href="Events.html">Events</a></li><li><a href="Matching.html">Matching</a></li><li><a href="Shared.html">Shared</a></li><li><a href="Sorting.html">Sorting</a></li><li><a href="Tags.html">Tags</a></li><li><a href="Triggers.html">Triggers</a></li><li><a href="Updating.html">Updating</a></li></ul><h3><a href="global.html">Global</a></h3>
</nav>

<br class="clear">

<footer>
    Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.0</a> on Thu Mar 01 2018 11:34:22 GMT+0000 (GMT)
</footer>

<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>
